﻿using Games.Common;
using Games.Data;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Games.Sudoku
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class SudokuPage : ContentPage
    {
        IDataBase database = DependencyService.Get<IDataBase>();
        private readonly GameInfo gameInfo;
        const double CellSpacing = 1;
        double cellSize;
        double layoutWidth, layoutHeight;
        SudokuLevel boxLevel;
        Matrix game;
        const int LENGTH = 9;
        bool isOptionsShown;
        History<Step> history = new History<Step>();
        //SudokuHistory sudokuHistory;
        //bool isInit = false;

        internal SudokuPage(GameInfo gameInfo)
        {
            database.CreateTable<SudokuRecord>();
            //database.CreateTable<SudokuHistory>();
            InitializeComponent();
            lblPrev.Text = "<<";
            lblNext.Text = ">>";
            tapSelect.Tapped += this.Tap_Tapped;
            tapDouble.Tapped += this.DblTap_Tapped;
            tapValue.Tapped += this.TapValue_Tapped;
            tapLevel.Tapped += this.TapLevel_Tapped;
            lblPrev.GestureRecognizers.Add(tapLevel);
            lblNext.GestureRecognizers.Add(tapLevel);
            BindingContext = this.gameInfo = gameInfo;
            game = new Matrix();
            LoadAsync().Wait();
        }

        async Task LoadAsync()
        {
            var gameArgs = await database.GetFirstOrDefaultAsync<GameArgs>(x => x.GameID == gameInfo.ID).ConfigureAwait(false);
            if(gameArgs != null)
            {
                boxLevel = BaseArgs.Convert<SudokuLevel>(gameArgs);
            }
            else
            {
                boxLevel = new SudokuLevel()
                {
                    GameID = gameInfo.ID,
                };
            }
            //sudokuHistory = await database.GetFirstOrDefaultAsync<SudokuHistory>(x => x.Level == boxLevel.Level && x.Index == boxLevel.Index).ConfigureAwait(false);
        }
        //async Task SaveHistory()
        //{
        //    if(sudokuHistory == null)
        //    {
        //        sudokuHistory = new SudokuHistory();
        //    }
        //    sudokuHistory.Index = boxLevel.Index;
        //    sudokuHistory.Level = boxLevel.Level;
        //    sudokuHistory.History = history.Serialize();
        //    sudokuHistory.CreateTime = DateTime.Now;
        //    sudokuHistory.Duration = TimeCounter.Stop();
        //    await database.SaveAsync(sudokuHistory);
        //}
        async Task SaveArgsAsync()
        {
            await database.SaveAsync(boxLevel.ToGameArgs());
        }
        async void OnLayoutSizeChanged(object sender, EventArgs args)
        {
            Layout layout = sender as Layout;
            layoutWidth = layout.Width;
            layoutHeight = layout.Height;
            await LoadMap();
            //if(sudokuHistory != null)
            //{
            //    history = new History<Step>(sudokuHistory.History);
            //}
        }

        async Task LoadMap()
        {
            history.Clear();
            var map = Data.Get(boxLevel.Level, boxLevel.Index);
            game.LoadMatrix(map);
            await SetButtons(map);
        }

        async Task SetButtons(MapData map)
        {
            if(map.Tip != null)
            {
                viewTip.IsVisible = true;
                lblTip.Text = map.Tip;
            }
            else
            {
                viewTip.IsVisible = false;
            }
            (lblPrev.Parent as View).IsVisible = boxLevel.Index > 0;
            (lblNext.Parent as View).IsVisible = boxLevel.Index < boxLevel.GetMax();
            await Task.Run(() => Device.BeginInvokeOnMainThread(() => InitLayout(map.Mask)));
        }

        async void TapLevel_Tapped(object sender, EventArgs e)
        {
            var lbl = sender as Label;
            if(lbl.Text[0] == '<')
            {
                await LevelChanged(false);
            }
            else
            {
                await LevelChanged(true);
            }
        }

        async Task LevelChanged(bool next)
        {
            var lv = boxLevel.Index;
            if(next)
            {
                if(boxLevel.Index < boxLevel.GetMax())
                {
                    lv = boxLevel.Index + 1;
                }
            }
            else if(boxLevel.Index > 0)
            {
                lv = boxLevel.Index - 1;
            }
            if(lv != boxLevel.Index)
            {
                boxLevel.Index = lv;
                await SaveArgsAsync();
                await LoadMap();
            }
        }

        private static LayoutOptions layoutOptions = new LayoutOptions(LayoutAlignment.Fill, true);

        private List<Label> lblValues = new List<Label>();
        private Dictionary<int, List<Label>> lblOptions = new Dictionary<int, List<Label>>();
        private Color normalBack = Color.WhiteSmoke, normalFore = Color.MediumBlue,
            fixFore = Color.Black, fixBack = Color.Gainsboro,
            focusBack = Color.Orange, focusFore = Color.White,
            highBack = Color.Lavender, highFore = Color.OrangeRed,
            emptyBack = Color.Transparent,
            errorFore = Color.Red, errorBack = Color.LightPink;
        private int fontSizeBig = 30, fontSizeSmall = 13;

        private List<Label> highValues = new List<Label>();
        private List<Label> errorValues = new List<Label>();
        private List<Label> highOptions = new List<Label>();
        Label current;
        TapGestureRecognizer tapSelect = new TapGestureRecognizer { NumberOfTapsRequired = 1 };
        TapGestureRecognizer tapDouble = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
        TapGestureRecognizer tapValue = new TapGestureRecognizer { NumberOfTapsRequired = 1 };
        TapGestureRecognizer tapLevel = new TapGestureRecognizer { NumberOfTapsRequired = 1 };

        void InitLayout(bool[,] mask)
        {
            btnNote.TextColor = Color.Silver;
            btnExcept.IsEnabled = boxLevel.Level > 0;
            btnNote.IsEnabled = boxLevel.Level > 0;
            isOptionsShown = false;
            isNote = false;
            current = null;
            gameInfo.Level = boxLevel.Level;
            gameInfo.LevelString = boxLevel.LevelString;
            gameInfo.No = boxLevel.No;

            var sizew = (layoutWidth - 4 + CellSpacing) / LENGTH;
            var sizeh = (layoutHeight - 4 + CellSpacing) / LENGTH;
            cellSize = Math.Min(sizew, sizeh);
            var laySize = LENGTH * cellSize - CellSpacing;
            var xMargin = (layoutWidth - laySize - 4) / 2.0;
            var yMargin = (layoutHeight - laySize - 4) / 2.0;
            board.Margin = new Thickness(xMargin, yMargin);
            board.WidthRequest = board.HeightRequest = laySize;
            shudu.Margin = new Thickness(2);
            shudu.Padding = new Thickness(0);
            viewTip.Margin = new Thickness(cellSize * 3 + xMargin + 3, cellSize * 3 + yMargin + 3, xMargin + 2, yMargin + 3);

            if(shudu.Children.Count == 0)
            {
                for(int r = 0; r < LENGTH; r++)
                {
                    for(int c = 0; c < LENGTH; c++)
                    {
                        var lbl = new Label
                        {
                            FontSize = fontSizeBig,
                            HorizontalOptions = layoutOptions,
                            VerticalOptions = layoutOptions,
                            HorizontalTextAlignment = TextAlignment.Center,
                            VerticalTextAlignment = TextAlignment.Center,
                            TextColor = normalFore,
                            BackgroundColor = normalBack,
                            HeightRequest = cellSize - 1,
                            FontAttributes = FontAttributes.Bold
                        };
                        lbl.Margin = CreateMargin(c, r);
                        lblValues.Add(lbl);
                        shudu.Children.Add(lbl, c, r);
                        lbl.GestureRecognizers.Add(tapSelect);
                        lbl.GestureRecognizers.Add(tapDouble);
                    }
                }
            }
            if(btns.Children.Count == 0)
            {
                for(int i = 0; i < LENGTH; i++)
                {
                    var btn = new Button
                    {
                        Text = (i + 1).ToString(),
                        Margin = new Thickness(0),
                        HeightRequest = 40,
                        IsEnabled = false
                    };
                    btn.Clicked += TapValue_Tapped;
                    btns.Children.Add(btn, i, 0);
                }
            }
            for(int r = 0; r < LENGTH; r++)
            {
                for(int c = 0; c < LENGTH; c++)
                {
                    var lbl = lblValues[r * 9 + c];
                    if(mask == null || mask[r, c] == true)
                    {
                        var v = game.Get(r, c);
                        if(v > 0)
                        {
                            if(game.IsFixed(r, c))
                            {
                                lbl.Text = v.ToString();
                                lbl.BackgroundColor = fixBack;
                                lbl.TextColor = fixFore;
                            }
                            else
                            {
                                lbl.Text = v.ToString();
                                lbl.BackgroundColor = normalBack;
                                lbl.TextColor = normalFore;
                            }
                        }
                        else
                        {
                            lbl.Text = null;
                            lbl.BackgroundColor = normalBack;
                            lbl.TextColor = normalFore;
                        }
                    }
                    else
                    {
                        lbl.Text = "*";
                        lbl.BackgroundColor = Color.DarkGray;
                        lbl.TextColor = Color.DimGray;
                    }
                    if(lblOptions.ContainsKey(r * 9 + c))
                    {
                        lblOptions[r * 9 + c].ForEach(x => x.IsVisible = false);
                    }
                }
            }
            ClearSelect();
            TimeCounter.Start();
            //isInit = true;
        }

        private void Tap_Tapped(object sender, EventArgs e)
        {
            if(game.IsFinished)
            {
                return;
            }
            var lbl = sender as Label;
            var r = Grid.GetRow(lbl);
            var c = Grid.GetColumn(lbl);
            lbl = lblValues[c + r * 9];
            if(lbl == current || lbl.Text == "*")
            {
                return;
            }
            ShowSelect(r, c);
            if(isExcept)
            {
                var opts = game.GetOptional(r, c).Select(x => x.ToString());
                foreach(var item in btns.Children.OfType<Button>())
                {
                    item.IsEnabled = opts.Contains(item.Text);
                }
            }
            else
            {
                foreach(var item in btns.Children.OfType<Button>())
                {
                    item.IsEnabled = true;
                }
            }
        }

        async void DblTap_Tapped(object sender, EventArgs e)
        {
            if(game.IsFinished || boxLevel.Level <= 0)
            {
                return;
            }
            var lbl = sender as Label;
            if(lbl.Text == "*")
            {
                return;
            }
            var r = Grid.GetRow(lbl);
            var c = Grid.GetColumn(lbl);
            if(game.Get(r, c) == 0)
            {
                var val = 0;
                var showOpts = lblOptions[c + r * 9].Where(x => x.IsVisible);
                if(showOpts.Count() == 1)
                {
                    val = showOpts.First().Text[0] - '0';
                }
                else if(showOpts.Count() == 0)
                {
                    var opts = game.GetOptional(r, c);
                    if(opts.Count == 1)
                    {
                        val = opts[0];
                    }
                }
                if(val > 0)
                {
                    FillValue(r, c, val);
                    if(game.IsFinished)
                    {
                        ClearSelect();
                        await ShowFinish();
                        return;
                    }
                    ShowSelect(r, c);
                }
            }
        }

        private void ShowSelect(int r, int c)
        {
            ClearSelect();
            var index = c + r * 9;
            var lbl = lblValues[index];
            current = lbl;
            lbl.BackgroundColor = focusBack;
            lbl.TextColor = focusFore;
            highValues.Add(lbl);
            var val = game.Get(r, c);
            if(val > 0)
            {
                var valStr = val.ToString();
                foreach(var item in lblValues)
                {
                    if(item.Text == valStr)
                    {
                        item.BackgroundColor = focusBack;
                        item.TextColor = focusFore;
                        highValues.Add(item);
                    }
                }
                foreach(var item in lblOptions.Values.SelectMany(x => x).Where(x => x.IsVisible))
                {
                    if(item.Text == valStr)
                    {
                        item.TextColor = highFore;
                        highOptions.Add(item);
                    }
                }
            }
            else
            {
                for(int i = 0; i < LENGTH; i++)
                {
                    if(i != r)
                    {
                        lblValues[i * 9 + c].BackgroundColor = highBack;
                        highValues.Add(lblValues[i * 9 + c]);
                    }
                    if(i != c)
                    {
                        lblValues[r * 9 + i].BackgroundColor = highBack;
                        highValues.Add(lblValues[r * 9 + i]);
                    }
                    int or = r / 3 * 3 + i % 3, oc = c / 3 * 3 + i / 3;
                    if(or != r && oc != c)
                    {
                        lblValues[or * 9 + oc].BackgroundColor = highBack;
                        highValues.Add(lblValues[or * 9 + oc]);
                    }
                }
                if(lblOptions.ContainsKey(index))
                {
                    foreach(var item in lblOptions[index])
                    {
                        item.TextColor = focusFore;
                        highOptions.Add(item);
                    }
                }
            }
            ShowErrors();
        }

        private void ClearSelect()
        {
            foreach(var lbl in highValues)
            {
                var r = Grid.GetRow(lbl);
                var c = Grid.GetColumn(lbl);
                if(lbl.Text == "*")
                {
                    lbl.BackgroundColor = Color.DarkGray;
                    lbl.TextColor = Color.DimGray;
                }
                else
                {
                    if(game.IsFixed(r, c))
                    {
                        lbl.BackgroundColor = fixBack;
                        lbl.TextColor = fixFore;
                    }
                    else
                    {
                        lbl.BackgroundColor = normalBack;
                        lbl.TextColor = normalFore;
                    }
                }
            }
            highValues.Clear();
            foreach(var lbl in highOptions)
            {
                lbl.TextColor = fixFore;
            }
            highOptions.Clear();
            current = null;
        }

        private void ShowErrors()
        {
            foreach(var lbl in errorValues)
            {
                var r = Grid.GetRow(lbl);
                var c = Grid.GetColumn(lbl);
                if(game.IsFixed(r, c))
                {
                    lbl.BackgroundColor = fixBack;
                    lbl.TextColor = fixFore;
                }
                else
                {
                    lbl.BackgroundColor = normalBack;
                    lbl.TextColor = normalFore;
                }
            }
            errorValues.Clear();
            foreach(var (r, c, v) in game.GetErrors())
            {
                if(!game.IsFixed(r, c))
                {
                    lblValues[r * 9 + c].TextColor = errorFore;
                }
                lblValues[r * 9 + c].BackgroundColor = errorBack;
                errorValues.Add(lblValues[r * 9 + c]);
            }
        }

        async void TapValue_Tapped(object sender, EventArgs e)
        {
            if(current == null || game.IsFinished)
            {
                return;
            }
            var r = Grid.GetRow(current);
            var c = Grid.GetColumn(current);
            if(game.IsFixed(r, c))
            {
                return;
            }
            var btn = sender as Button;
            var val = btn.Text[0] - '0';
            var v = game.Get(r, c);
            if(v == val)
            {
                return;
            }
            var lastVal = v;
            if(lastVal > 0)
            {
                lblValues[r * 9 + c].Text = null;
                game.ClearValue(r, c);
            }
            if(isNote)
            {
                var lbl = CreateOption(r, c, val);
                lbl.IsVisible ^= true;
                history.Push(new Step
                {
                    Row = r,
                    Col = c,
                    OldValue = lbl.IsVisible ? 0 : val,
                    NewValue = lbl.IsVisible ? val : 0,
                    IsOption = true
                });
            }
            else
            {
                FillValue(r, c, val, lastVal);
                if(game.IsFinished)
                {
                    ClearSelect();
                    await ShowFinish();
                    return;
                }
            }
            ShowSelect(r, c);
        }

        void FillValue(int r, int c, int val, int lastVal = 0)
        {
            var step = new Step
            {
                Row = r,
                Col = c,
                NewValue = val,
                OldValue = lastVal,
                SubSteps = new List<Step>()
            };
            game.FillValue(r, c, val);
            lblValues[r * 9 + c].Text = val.ToString();
            if(lblOptions.ContainsKey(r * 9 + c))
            {
                lblOptions[r * 9 + c].ForEach(x => x.IsVisible = false);
            }
            var strVal = val.ToString();
            for(int i = 0; i < LENGTH; i++)
            {
                if(i != r && !game.IsFixed(i, c) && lblOptions.ContainsKey(i * 9 + c))
                {
                    var lblopt = lblOptions[i * 9 + c].FirstOrDefault(x => x.Text == strVal);
                    if(lblopt != null && lblopt.IsVisible)
                    {
                        lblopt.IsVisible = false;
                        step.SubSteps.Add(new Step { Row = i, Col = c, IsOption = true, OldValue = val });
                    }
                }
                if(i != c && !game.IsFixed(r, i) && lblOptions.ContainsKey(r * 9 + i))
                {
                    var lblopt = lblOptions[r * 9 + i].FirstOrDefault(x => x.Text == strVal);
                    if(lblopt != null && lblopt.IsVisible)
                    {
                        lblopt.IsVisible = false;
                        step.SubSteps.Add(new Step { Row = r, Col = i, IsOption = true, OldValue = val });
                    }
                }
                int or = r / 3 * 3 + i % 3, oc = c / 3 * 3 + i / 3;
                if(or != r && oc != c && !game.IsFixed(or, oc) && lblOptions.ContainsKey(or * 9 + oc))
                {
                    var lblopt = lblOptions[or * 9 + oc].FirstOrDefault(x => x.Text == strVal);
                    if(lblopt != null && lblopt.IsVisible)
                    {
                        lblopt.IsVisible = false;
                        step.SubSteps.Add(new Step { Row = or, Col = oc, IsOption = true, OldValue = val });
                    }
                }
            }
            history.Push(step);
        }

        private Thickness CreateMargin(int left, int top, bool isOptions = false)
        {
            int ml = 1, mt = 1, mr = 1, mb = 1;
            if(left % 3 == 0)
            {
                ml = 1;
                mr = 0;
            }
            else if(left % 3 == 1)
            {
                ml = mr = 0;
            }
            else if(left % 3 == 2)
            {
                ml = 0;
                mr = 1;
            }
            if(top % 3 == 0)
            {
                mt = 1;
                mb = 0;
            }
            else if(top % 3 == 1)
            {
                mt = mb = 0;
            }
            else if(top % 3 == 2)
            {
                mt = 0;
                mb = 1;
            }
            if(isOptions)
            {
                ml += 3;
                mr += 3;
            }
            return new Thickness(ml, mt, mr, mb);
        }

        private Label CreateOption(int r, int c, int v)
        {
            var index = c + r * 9;
            if(!lblOptions.ContainsKey(index))
            {
                lblOptions.Add(index, new List<Label>());
            }
            var list = lblOptions[index];
            var lbl = list.FirstOrDefault(x => x.Text == v.ToString());
            if(lbl == null)
            {
                lbl = new Label
                {
                    FontSize = fontSizeSmall,
                    HorizontalTextAlignment = (TextAlignment)((v - 1) % 3),
                    VerticalTextAlignment = (TextAlignment)((v - 1) / 3),
                    TextColor = fixFore,
                    BackgroundColor = emptyBack,
                    Text = v.ToString(),
                    Margin = CreateMargin(c, r, true)
                };
                lbl.GestureRecognizers.Add(tapSelect);
                lbl.GestureRecognizers.Add(tapDouble);
                shudu.Children.Add(lbl, c, r);
                list.Add(lbl);
            }
            return lbl;
        }

        private void Undo_Clicked(object sender, EventArgs e)
        {
            if(game.IsFinished)
            {
                return;
            }
            history.Undo(step => DoStep(step, true));
        }
        private void Redo_Clicked(object sender, EventArgs e)
        {
            if(game.IsFinished)
            {
                return;
            }
            history.Redo(step => DoStep(step, false));
        }

        private void DoStep(Step step, bool isReverse)
        {
            if(step == null)
            {
                return;
            }
            if(step.IsOption)
            {
                ToggleOptionByStep(step, isReverse);
                if(step.SubSteps != null)
                {
                    foreach(var item in step.SubSteps)
                    {
                        ToggleOptionByStep(item, isReverse);
                    }
                }
            }
            else
            {
                var index = step.Col + step.Row * 9;
                var val = isReverse ? step.OldValue : step.NewValue;
                if(val == 0)
                {
                    lblValues[index].Text = null;
                    game.ClearValue(step.Row, step.Col);
                }
                else
                {
                    lblValues[index].Text = val.ToString();
                    game.FillValue(step.Row, step.Col, val);
                }
                if(step.SubSteps != null)
                {
                    foreach(var item in step.SubSteps)
                    {
                        ToggleOptionByStep(item, isReverse);
                    }
                }
            }
        }

        private void ToggleOptionByStep(Step step, bool isReverse)
        {
            if(step.IsOption)
            {
                if(step.OldValue != 0)
                {
                    lblOptions[step.Col + step.Row * 9].FirstOrDefault(x => x.Text == step.OldValue.ToString()).IsVisible = isReverse;
                }
                else if(step.NewValue != 0)
                {
                    lblOptions[step.Col + step.Row * 9].FirstOrDefault(x => x.Text == step.NewValue.ToString()).IsVisible = !isReverse;
                }
            }
        }

        private void FillNote_Clicked()
        {
            for(int r = 0; r < LENGTH; r++)
            {
                for(int c = 0; c < LENGTH; c++)
                {
                    if(game.Get(r, c) == 0)
                    {
                        var opts = game.GetOptional(r, c);
                        foreach(var i in opts)
                        {
                            var lbl = CreateOption(r, c, i);
                            lbl.IsVisible = true;
                        }
                    }
                }
            }
        }
        private void Clear_Clicked(object sender, EventArgs e)
        {
            if(current == null || game.IsFinished)
            {
                return;
            }
            var r = Grid.GetRow(current);
            var c = Grid.GetColumn(current);
            if(game.IsFixed(r, c))
            {
                return;
            }
            var val = game.Get(r, c);
            if(val > 0)
            {
                lblValues[r * 9 + c].Text = null;
                game.ClearValue(r, c);
                ShowSelect(r, c);

                history.Push(new Step
                {
                    Row = r,
                    Col = c,
                    OldValue = val
                });
            }
            else
            {
                var step = new Step
                {
                    Row = r,
                    Col = c,
                    IsOption = true,
                    SubSteps = new List<Step>()
                };
                if(lblOptions.ContainsKey(r * 9 + c))
                {
                    foreach(var item in lblOptions[c + r * 9])
                    {
                        if(item.IsVisible)
                        {
                            item.IsVisible = false;
                            step.SubSteps.Add(new Step { Row = r, Col = c, IsOption = true, OldValue = int.Parse(item.Text) });
                        }
                    }
                }
                history.Push(step);
            }
        }
        private bool isNote, isExcept;
        private void Note_Clicked(object sender, EventArgs e)
        {
            if(game.IsFinished)
            {
                return;
            }
            isNote ^= true;
            if(!isOptionsShown && isNote)
            {
                FillNote_Clicked();
                ClearSelect();
                isOptionsShown = true;
            }
            (sender as Button).TextColor = isNote ? Color.Orange : Color.Silver;
            foreach(var item in btns.Children.OfType<Button>())
            {
                item.IsEnabled = current != null;
            }
        }
        private void Except_Clicked(object sender, EventArgs e)
        {
            if(game.IsFinished)
            {
                return;
            }
            isExcept ^= true;
            (sender as Button).TextColor = isExcept ? Color.Orange : Color.Silver;
            if(current == null)
            {
                foreach(var item in btns.Children.OfType<Button>())
                {
                    item.IsEnabled = false;
                }
                return;
            }
            if(!isExcept)
            {
                foreach(var item in btns.Children.OfType<Button>())
                {
                    item.IsEnabled = true;
                }
            }
            else
            {
                var list = new List<string>();
                var r = Grid.GetRow(current);
                var c = Grid.GetColumn(current);
                if(!game.IsFixed(r, c))
                {
                    var opts = game.GetOptional(r, c).Select(x => x.ToString());
                    list.AddRange(opts);
                }
                foreach(var item in btns.Children.OfType<Button>())
                {
                    item.IsEnabled = list.Contains(item.Text);
                }
            }
        }
        async Task ShowFinish()
        {
            foreach(var item in btns.Children.OfType<Button>())
            {
                item.IsEnabled = false;
            }
            if(boxLevel.Increase(gameInfo.Level))
            {
                await SaveArgsAsync();
            }
            var record = await SaveRecordAsync();
            await DisplayAlert("恭喜", $"恭喜完成游戏\r\n{record.Message}", "OK");
            await LoadMap();
        }
        async Task<Record> SaveRecordAsync()
        {
            var dur = TimeCounter.Stop();
            var cRecord = new SudokuRecord()
            {
                GameID = gameInfo.ID,
                CreateTime = DateTime.Now,
                No = boxLevel.No,
                LevelString = boxLevel.LevelString,
                Duration = dur
            };
            await database.SaveRecordByDiff(cRecord, 1,
                o => o.No == cRecord.No,
                list => list.OrderBy(x => x.Duration),
                o => o.Duration > cRecord.Duration
            );
            return cRecord.Convert();
        }
        void LevelClicked(object sender, EventArgs args)
        {
            if(!mask.Contains("levels"))
            {
                var (view, buttons) = ControlUtil.GetLevelGrid(SudokuLevel.LEVELS, -1, 1, LevelSelect);
                mask.SetSize("levels", 0.8, 0.64);
                mask.Add("levels", "选择难度", view);
            }
            mask.ShowWithCancel("levels");
        }
        async void LevelSelect(string level)
        {
            mask.Hide();
            var lv = SudokuLevel.LEVELS.IndexOf(level);
            if(boxLevel.Level != lv)
            {
                boxLevel.Level = lv;
                boxLevel.Index = boxLevel.GetMax();
                await SaveArgsAsync();
            }
            await LoadMap();
        }
        async void RestartClicked(object sender, EventArgs args)
        {
            await LoadMap();
        }
        void OnAboutButtonClicked(object sender, EventArgs args)
        {
            if(!mask.Contains("about"))
            {
                var aboutView = ControlUtil.GetAbout(gameInfo);
                mask.Add("about", gameInfo.Name, aboutView);
            }
            mask.ShowWithCancel("about", "确定");
        }
        void OnRecordClicked(object sender, EventArgs args)
        {
            Navigation.PushAsync(new RecordPage(gameInfo.Name, new RecordViewModel<SudokuRecord>(null)));
        }

        //protected override void OnAppearing()
        //{
        //    if(isInit)
        //    {
        //        TimeCounter.Start(sudokuHistory?.Duration);
        //    }
        //    base.OnAppearing();
        //}

        //protected override async void OnDisappearing()
        //{
        //    await SaveHistory();
        //    base.OnDisappearing();
        //}

        class Step
        {
            [JsonProperty("r")]
            public int Row { get; set; }
            [JsonProperty("c")]
            public int Col { get; set; }
            [JsonProperty("o")]
            public int OldValue { get; set; }
            [JsonProperty("n")]
            public int NewValue { get; set; }
            [JsonProperty("p")]
            public bool IsOption { get; set; }
            [JsonProperty("subs")]
            public List<Step> SubSteps { get; set; }
        }
    }
}